#pragma once

#include "BaseObject.hpp"

class NxSceneDesc;
class NxScene;

class KDNode;
class KDTree;
class Material;
class ParamsCamera;

class SceneObject;
class Scene;

class Component
{
	friend class SceneObject;

public:
	Component() : m_SceneObject(NULL) { }
	virtual ~Component() { }
	SceneObject * GetOwnerSceneObject() { return m_SceneObject; }

protected:
	virtual void OnUpdate() { }

private:
	SceneObject *m_SceneObject;
};

enum RENDER_PASS
{
	RENDER_PASS_SHADOW_MAP = 0x01,
	RENDER_PASS_GBUFFER    = 0x02,
	RENDER_PASS_FORWARD    = 0x04,
};

struct RENDER_ITEM
{
	uint Id;
	uint RenderPasses;
	Material *Mat; // Not NULL for standard material rendering.
	float SortingDepthBias; // Used only in forward pass.
	const MATRIX *ExtraVertexTransform; // Can be NULL.
	const MATRIX *TexCoordTransform; // Can be NULL.
	const MATRIX *BoneMatrices; // Pointer to array or NULL if no skinning.
	uint BoneCount;

	RENDER_ITEM()
	{
	}

	RENDER_ITEM(
		uint id,
		uint renderPasses,
		Material *mat,
		float sortingDepthBias = 0.f,
		const MATRIX *extraVertexTransform = NULL,
		const MATRIX *texCoordTransform = NULL,
		const MATRIX *boneMatrices = NULL,
		uint boneCount = 0)
		: Id(id)
		, RenderPasses(renderPasses)
		, Mat(mat)
		, SortingDepthBias(sortingDepthBias)
		, ExtraVertexTransform(extraVertexTransform)
		, TexCoordTransform(texCoordTransform)
		, BoneMatrices(boneMatrices)
		, BoneCount(boneCount)
	{
	}
};

typedef std::vector<RENDER_ITEM> RenderItemVector;

class RenderingComponent : public Component
{
public:
	bool Visible;

	RenderingComponent();
	virtual ~RenderingComponent();
	virtual void GetRenderItems(RenderItemVector &outItems) = 0;
	// Used for render items with material.
	virtual void RenderGeometry(uint itemId) = 0;
	// Used for render items without material.
	// renderContext: pointer to one of RENDER_CONTEXT_ structures (Renderer.hpp) dependant on pass type.
	virtual void RenderCustom(uint itemId, RENDER_PASS pass, const void *renderContext) = 0;
};

class LightComponent : public Component
{
public:
	bool Enabled;
	COLORF Color;
	float Range;
	bool SpecularEnabled;
	bool ShadowEnabled;
	float ShadowFactor;
	float ShadowMapDepthBias;

	LightComponent();

	virtual void UpdateObjectBoundingBox() = 0;
	virtual bool IsVisible();
};

class SceneObject : public BaseObject
{
	friend class SceneObjectIterator;
	friend class SceneObjectSiblingIterator;
	friend class SceneObjectTreeIterator;
	friend class Scene;
	friend class KDTree;

public:
	SceneObject();

	virtual SceneObject * AsSceneObject() { return this; }

	// You can set owner scene at most once.
	void SetScene(Scene *v);
	Scene * GetScene() { return m_Scene; }

	SceneObject * GetParent() { return m_Parent; }
	void SetParent(SceneObject *obj);

	SceneObjectSiblingIterator ChildObjectsBegin();
	SceneObjectSiblingIterator ChildObjectsEnd();
	SceneObjectTreeIterator SubtreeBegin();
	SceneObjectTreeIterator SubtreeEnd();

	////// Transform components relative to parent
	const common::VEC3 & GetPosition() { return m_Position; }
	const common::QUATERNION & GetOrientation() { return m_Orientation; }
	const common::VEC3 & GetScaling() { return m_Scaling; }
	const common::BOX & GetBoundingBox() { return m_BoundingBox; }
	void SetPosition(const common::VEC3 &v) { m_Position = v; InvalidateTransformData(); }
	void SetOrientation(const common::QUATERNION &v) { m_Orientation = v; InvalidateTransformData(); }
	void SetScaling(const common::VEC3 &v) { m_Scaling = v; InvalidateTransformData(); }
	void SetScaling(float v) { m_Scaling = common::VEC3(v, v, v); InvalidateTransformData(); }
	void SetBoundingBox(const common::BOX &v) { m_BoundingBox = v; InvalidateTransformData(); }

	const common::MATRIX & GetLocalToParentTransform() { if (!m_TransformDataPresent) CalcTransformData(); return m_LocalToParentTransform; }
	const common::MATRIX & GetLocalToWorldTransform() { if (!m_TransformDataPresent) CalcTransformData(); return m_LocalToWorldTransform; }
	const common::VEC3 & GetWorldPosition() { return *(common::VEC3*)&GetLocalToWorldTransform()._41; }
	const common::BOX & GetWorldBoundingBox() { if (!m_TransformDataPresent) CalcTransformData(); return m_WorldBoundingBox; }

	RenderingComponent * GetRenderingComponent() { return m_RenderingComponent; }
	LightComponent * GetLightComponent() { return m_LightComponent; }
	// Obiekt sam zwolni przekazany komponent przy swoim niszczeniu lub przy zmianie na inny komponent.
	void SetRenderingComponent(RenderingComponent *renderingComponent);
	void SetLightComponent(LightComponent *component);

protected:
	virtual ~SceneObject();
	// Calls Update to all its components.
	virtual void OnUpdate();

private:
	Scene *m_Scene;
	SceneObject *m_Parent;
	SceneObject *m_FirstChild, *m_LastChild;
	SceneObject *m_PrevSibling, *m_NextSibling;
	SceneObject *m_PrevXformed, *m_NextXformed; bool m_IsOnXformedList;

	common::VEC3 m_Position;
	common::QUATERNION m_Orientation;
	common::VEC3 m_Scaling;
	common::BOX m_BoundingBox;

	////// Lazy evaluated transform data
	bool m_TransformDataPresent;
	common::MATRIX m_LocalToParentTransform, m_LocalToWorldTransform;
	common::BOX m_WorldBoundingBox;

	RenderingComponent *m_RenderingComponent;
	LightComponent *m_LightComponent;

	void AddChild(SceneObject *obj);
	void RemoveChild(SceneObject *obj);

	////// For class Scene
	SceneObject *m_PrevSceneObject, *m_NextSceneObject;

	////// For class KDTree
	KDNode *m_KDNode;

	void InvalidateTransformData();
	void CalcTransformData();
};

typedef std::vector<SceneObject*> SceneObjectPtrVector;

class SceneObjectPredicate
{
public:
	virtual ~SceneObjectPredicate() { }
	virtual bool Test(SceneObject *obj) = 0;
};

// Forward iterator
class SceneObjectIterator
{
	friend class Scene;

public:
	SceneObjectIterator() : m_Obj(NULL) { }

	SceneObjectIterator & operator ++ () { m_Obj = m_Obj->m_NextSceneObject; return *this; }
	SceneObjectIterator operator ++ (int) { SceneObjectIterator r = *this; operator++(); return r; }
	SceneObject & operator * () const { return *m_Obj; }
	SceneObject * operator -> () const { return m_Obj; }
	bool operator == (const SceneObjectIterator &it) const { return m_Obj == it.m_Obj; }
	bool operator != (const SceneObjectIterator &it) const { return m_Obj != it.m_Obj; }

private:
	SceneObject *m_Obj;

	////// For class Engine
	SceneObjectIterator(SceneObject *Obj) : m_Obj(Obj) { }
};

// Forward iterator
class SceneObjectSiblingIterator
{
	friend class Scene;
	friend class SceneObject;

public:
	SceneObjectSiblingIterator() : m_Obj(NULL) { }

	SceneObjectSiblingIterator & operator ++ () { m_Obj = m_Obj->m_NextSibling; return *this; }
	SceneObjectSiblingIterator operator ++ (int) { SceneObjectSiblingIterator r = *this; operator++(); return r; }
	SceneObject & operator * () const { return *m_Obj; }
	SceneObject * operator -> () const { return m_Obj; }
	bool operator == (const SceneObjectSiblingIterator &it) const { return m_Obj == it.m_Obj; }
	bool operator != (const SceneObjectSiblingIterator &it) const { return m_Obj != it.m_Obj; }

private:
	SceneObject *m_Obj;

	////// For class Engine
	SceneObjectSiblingIterator(SceneObject *Obj) : m_Obj(Obj) { }
};

// Forward iterator
class SceneObjectTreeIterator
{
	friend class Scene;
	friend class SceneObject;

public:
	SceneObjectTreeIterator() : m_Root(NULL), m_Obj(NULL) { }

	SceneObjectTreeIterator & operator ++ ()
	{
		if (m_Obj->m_FirstChild) m_Obj = m_Obj->m_FirstChild;
		else if (m_Obj->m_NextSibling) m_Obj = m_Obj->m_NextSibling;
		else GoToParent();
		return *this;
	}

	SceneObjectTreeIterator operator ++ (int) { SceneObjectTreeIterator r = *this; operator++(); return r; }
	SceneObject & operator * () const { return *m_Obj; }
	SceneObject * operator -> () const { return m_Obj; }
	bool operator == (const SceneObjectTreeIterator &it) const { return m_Obj == it.m_Obj; }
	bool operator != (const SceneObjectTreeIterator &it) const { return m_Obj != it.m_Obj; }

private:
	SceneObject *m_Root, *m_Obj;

	////// For class Engine
	SceneObjectTreeIterator(SceneObject *Root, SceneObject *Obj) : m_Root(Root), m_Obj(Obj) { }

	void GoToParent();
};

class ISky : public BaseObject
{
public:
	// Should perform Z-test with output Z = 1.0.
	virtual bool Render(const ParamsCamera &camera) = 0;
};

class Scene
{
	friend class SceneObject;
	friend class SceneObjectIterator;
	friend class SceneObjectSiblingIterator;

public:
	Scene(common::BOX &WorldBox, const NxSceneDesc &physicsDesc);
	~Scene();

	void ApplyDeferredObjectTransforms();

	const common::BOX & GetWorldBox() { return m_WorldBox; }
	uint GetObjectCount() { return m_ObjectCount; }
	KDTree * GetKDTree() { return m_KDTree.get(); }

	SceneObjectIterator ObjectsBegin() { return SceneObjectIterator(m_FirstObject); }
	SceneObjectIterator ObjectsEnd() { return SceneObjectIterator(NULL); }
	SceneObjectIterator ObjectToIterator(SceneObject *obj) { return SceneObjectIterator(obj); }

	SceneObjectSiblingIterator TopObjectsBegin() { return SceneObjectSiblingIterator(m_FirstTopObject); }
	SceneObjectSiblingIterator TopObjectsEnd() { return SceneObjectSiblingIterator(NULL); }
	SceneObjectSiblingIterator ObjectToSiblingIterator(SceneObject *obj) { return SceneObjectSiblingIterator(obj); }

	SceneObjectTreeIterator ObjectTreeBegin() { return SceneObjectTreeIterator(NULL, m_FirstTopObject); }
	SceneObjectTreeIterator ObjectTreeEnd() { return SceneObjectTreeIterator(NULL, NULL); }
	SceneObjectTreeIterator ObjectToTreeIterator(SceneObject *obj) { return SceneObjectTreeIterator(obj, obj); }

	void ListAllObjects(SceneObjectPtrVector &out, SceneObjectPredicate *Pred = NULL);
	void ListObjectsIntersectingFrustum(SceneObjectPtrVector &out, const common::FRUSTUM_PLANES &Frustum, SceneObjectPredicate *Pred = NULL);
	void ListObjectsIntersectingSweptBox(SceneObjectPtrVector &out, const common::BOX &box, const common::VEC3 &boxDir, SceneObjectPredicate *Pred = NULL);
	//void ListObjectsIntersectingBox(SceneObjectPtrVector &out, const common::BOX &Box, SceneObjectPredicate *Pred = NULL);
	void ListObjectsIntersectingSphere(SceneObjectPtrVector &out, const common::VEC3 &sphereCenter, float sphereRadius, SceneObjectPredicate *pred = NULL);

	NxScene * GetPhysics() { return m_PhysxScene; }

	ISky * GetSky() { return m_Sky; }
	void SetSky(ISky *sky);

private:
	common::BOX m_WorldBox;
	common::scoped_ptr<KDTree> m_KDTree;
	uint m_ObjectCount;
	SceneObject *m_FirstObject, *m_LastObject;
	SceneObject *m_FirstTopObject, *m_LastTopObject;
	SceneObject *m_FirstXformed, *m_LastXformed;
	bool m_InsideDestructor;

	NxScene *m_PhysxScene;

	ISky *m_Sky;

	void CreatePhysics(const NxSceneDesc &physicsDesc);
	void DestroyPhysics();

	////// For friend classes
	void RegisterObject(SceneObject *obj);
	void UnregisterObject(SceneObject *obj);
	void AddTopObject(SceneObject *obj);
	void RemoveTopObject(SceneObject *obj);
	void ObjectTransformChange(SceneObject *obj);
};
